/*
 * Copyright (C) 2016, apexes.net. All rights reserved.
 * 
 *        http://www.apexes.net
 * 
 */
package net.apexes.wsonrpc.server.nano;

import net.apexes.wsonrpc.core.JsonRpcEngine;
import net.apexes.wsonrpc.core.JsonRpcLogger;
import net.apexes.wsonrpc.core.ServiceRegistry;
import net.apexes.wsonrpc.core.Transport;
import net.apexes.wsonrpc.json.JsonImplementor;
import net.apexes.wsonrpc.json.support.gson.GsonImplementor;
import net.apexes.wsonrpc.server.PathAcceptor;
import net.apexes.wsonrpc.server.PathAcceptors;
import net.apexes.wsonrpc.server.http.HttpRequestValidator;
import net.apexes.wsonrpc.server.nano.http.IHTTPSession;
import net.apexes.wsonrpc.server.nano.http.NanoHTTPD;
import net.apexes.wsonrpc.server.nano.http.request.Method;
import net.apexes.wsonrpc.server.nano.http.response.Response;
import net.apexes.wsonrpc.server.nano.http.response.Status;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Locale;

/**
 * 
 * @author <a href="mailto:hedyn@foxmail.com">HeDYn</a>
 *
 */
public class NanoJsonRpcServer extends NanoHTTPD {

    private final PathAcceptor pathAcceptor;
    private final JsonRpcEngine jsonRpcEngine;
    private HttpRequestValidator requestValidator;

    public NanoJsonRpcServer(int port) {
        this(port, PathAcceptors.anyPath());
    }
    
    public NanoJsonRpcServer(int port, PathAcceptor pathAcceptor) {
        this(port, pathAcceptor, GsonImplementor.DEFAULT);
    }

    public NanoJsonRpcServer(int port, PathAcceptor pathAcceptor, JsonImplementor jsonImpl) {
        super(port);
        this.pathAcceptor = pathAcceptor;
        this.jsonRpcEngine = new JsonRpcEngine(jsonImpl);
    }
    
    public ServiceRegistry getServiceRegistry() {
        return jsonRpcEngine.getServiceRegistry();
    }

    public JsonRpcLogger getJsonRpcLogger() {
        return jsonRpcEngine.getJsonRpcLogger();
    }

    public void setJsonRpcLogger(JsonRpcLogger jsonRpcLogger) {
        jsonRpcEngine.setJsonRpcLogger(jsonRpcLogger);
    }

    public HttpRequestValidator getRequestValidator() {
        return requestValidator;
    }

    public void setRequestValidator(HttpRequestValidator requestValidator) {
        this.requestValidator = requestValidator;
    }

    @Override
    public Response serve(IHTTPSession session) {
        String uri = session.getUri();
        if (!pathAcceptor.accept(uri)) {
            return responseStatue(Status.FORBIDDEN);
        }

        Method method = session.getMethod();
        if (Method.POST.equals(method)) {
            try {
                ByteArrayOutputStream out = new ByteArrayOutputStream();
                session.handleBody(out);
                byte[] bodyBytes = out.toByteArray();

                HttpRequestValidator validator = requestValidator;
                if (validator != null && !validator.validate(key -> getHeader(session, key), bodyBytes)) {
                    return responseStatue(Status.FORBIDDEN);
                }

                TransportImpl transport = new TransportImpl();
                jsonRpcEngine.receiveRequest(bodyBytes, transport, session.getRemoteIpAddress());
                return Response.newFixedLengthResponse(transport.json);
            } catch (Exception e) {
                return responseStatue(Status.INTERNAL_ERROR, e.toString());
            }
        }
        return responseStatue(Status.NOT_FOUND);
    }

    private static String getHeader(IHTTPSession session, String key) {
        return session.getHeaders().get(key.toLowerCase(Locale.US));
    }

    /**
     * 
     * @author <a href="mailto:hedyn@foxmail.com">HeDYn</a>
     *
     */
    private static class TransportImpl implements Transport {

        private String json;

        @Override
        public void sendBinary(byte[] bytes) throws IOException {
            this.json = new String(bytes, StandardCharsets.UTF_8);
        }

    }

    private static Response responseStatue(Status status) {
        return responseStatue(status, status.getDescription());
    }

    private static Response responseStatue(Status status, String message) {
        return Response.newFixedLengthResponse(status, NanoHTTPD.MIME_PLAINTEXT, message);
    }
    
}
